package common.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import javax.persistence.Table;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.DecimalFormat;
import java.util.*;

/**
 * description:
 * @author yanxf@winning.com.cn
 * @date 2020/04/07 18:21
 */
@Slf4j
@Component
public class BatchInsertUtils {

  @Resource
  private JdbcTemplateRepository jdbcTemplateRepository;

  @Value("${dialect}")
  public String dialect;

  private int defaultSize = 200;

  /**
   * 批量插入数据
   *
   * @param datas 更新对象集合
   * @param clazz 对象类型
   *
   * @throws Exception
   */
  public void execute(Collection datas, Class clazz) throws Exception {

    execute(datas, clazz, defaultSize);
  }

  /**
   * 批量插入数据
   *
   * @param datas 更新对象集合
   * @param clazz 对象类型
   * @param size  批量sql条数
   */
  public void execute(Collection datas, Class clazz, int size) throws Exception {

    List<String> batchSQL = buildSQL(datas, clazz, size);

    if (CollectionUtils.isEmpty(batchSQL)) {
      return;
    }
    Table table = (Table) clazz.getAnnotation(Table.class);
    String tableName = table.name();
    for (String sql : batchSQL) {
      //logger.debug(sql);
      log.info("批量插入数据，{}：", tableName);
      jdbcTemplateRepository.getJdbcTemplate().execute(sql);
    }
  }

  /**
   * 创建批量sql
   *
   * @param clazz
   *
   * @return
   */
  private List<String> buildSQL(Collection datas, Class clazz, int splitSize) throws Exception {

    if (CollectionUtils.isEmpty(datas)) {
      return null;
    }
    List<String> batchSQLs = new ArrayList<>();
    String headerSql = createInsertHeader(clazz);
    int dateSize = datas.size();
    int pageNum = 1;
    StringBuilder fieldSb = new StringBuilder();
    Iterator<String> iterator = datas.iterator();
    for (int i = 0; iterator.hasNext(); i++) {
      int offSet = (dateSize - (splitSize * (pageNum - 1))) > splitSize ? splitSize * pageNum : dateSize;
      if("oracle".equals(dialect)) {
        fieldSb.append(" select ");
      }
      fieldSb.append(createInsertValue(iterator.next(), clazz));
        if("oracle".equals(dialect)) {
          fieldSb.append(" from dual union all ");
        }
      if ((offSet - 1) == i) {
        String fieldStr;
        if("oracle".equals(dialect)) {
          fieldStr = StringUtils.substringBeforeLast(fieldSb.toString(), "union all");
        } else {
          fieldStr = StringUtils.substringBeforeLast(fieldSb.toString(), ",");
        }

        String result = headerSql + fieldStr;
        batchSQLs.add(result);
        pageNum++;
        fieldSb.setLength(0);
      }
    }
    return batchSQLs;
  }

  /**
   * 创建取值sql
   *
   * @param clazz
   *
   * @return
   *
   * @throws Exception
   */
  private StringBuilder createInsertValue(Object obj, Class clazz) throws Exception {

    StringBuilder fieldSb = new StringBuilder();
    if(!"oracle".equals(dialect)) {
      fieldSb.append("(");
    }
    Map<String, String> param = getParam(clazz);

    int index = 1;
    for (Map.Entry<String, String> entry : param.entrySet()) {


      String methodName = "get" + StringUtils.capitalize(entry.getKey());
      Method method;
      try {
        method = clazz.getDeclaredMethod(methodName);
      } catch (NoSuchMethodException e) {
        method = clazz.getSuperclass().getDeclaredMethod(methodName);
      }

      Object value = method.invoke(obj);
      if (value != null) {
        switch (entry.getValue()) {
          case "java.util.Date":
            String dateStr = DateFormatUtils.format((Date) value, "yyyy-MM-dd HH:mm:ss");
            value = dateStr;
            break;
          case "java.math.BigDecimal":
            DecimalFormat df = new DecimalFormat("#0.00");
            value = df.format(value);
            break;
          default:

        }
      }
      if (value instanceof String) {
        String valuestr = String.valueOf(value);
        valuestr = StringUtils.replaceEach(valuestr, new String[]{"'"}, new String[]{"\\'"});

        if ("java.util.Date".equals(entry.getValue()) &&"oracle".equals(dialect)) {
          fieldSb.append("to_date('").append(valuestr).append("', 'yyyy-mm-dd hh24:mi:ss')");
        } else {
          fieldSb.append("'").append(valuestr).append("'");
        }
      } else {
        fieldSb.append(value);
      }
      if (index < param.size()) {
        fieldSb.append(",");
      }
      index++;
    }
    if(!"oracle".equals(dialect)) {
      fieldSb.append("),");
    }
    return fieldSb;
  }

  /**
   * 创建头部sql
   *
   * @param clazz
   *
   * @return
   *
   * @throws Exception
   */
  private String createInsertHeader(Class clazz) throws Exception {

    Map<String, String> param = getParam(clazz);
    StringBuilder fieldSb = new StringBuilder();
    param.forEach((key, value) -> fieldSb.append("" + key + ", "));
    String fieldStr = StringUtils.substringBeforeLast(fieldSb.toString(), ",");

    Table table = (Table) clazz.getAnnotation(Table.class);
    String tableName = table.name();
    String headerSql = "INSERT INTO " + StringUtils.uncapitalize(tableName) + " (" + fieldStr + ")";
      if(!"oracle".equals(dialect)) {
          headerSql = headerSql + " VALUES ";
      }
    return headerSql;
  }

  /**
   * 获取表字段
   *
   * @param clazz
   *
   * @return
   *
   * @throws Exception
   */
  private Map<String, String> getParam(Class clazz) throws Exception {

    Map<String, String> fields = new LinkedHashMap<>();

    //处理本类字段
    Field[] declaredFields = clazz.getDeclaredFields();
    for (Field field : declaredFields) {
      String name = field.getName();
      if ("serialVersionUID".equalsIgnoreCase(name)) {
        continue;
      }
      fields.put(name, field.getType().getName());
    }

    //过滤自增字段、非持久化字段
    Method[] declaredMethods = clazz.getDeclaredMethods();
    for (Method method : declaredMethods) {
      for (Annotation annotation : method.getDeclaredAnnotations()) {
        String field = StringUtils.uncapitalize(StringUtils.substringAfter(method.getName(), "get"));
        String annotationName = annotation.annotationType().getName();
        if ("javax.persistence.GeneratedValue".equals(annotationName) || "javax.persistence.Transient".equals(annotationName)
                || "com.baomidou.mybatisplus.annotation.TableField".equals(annotationName)) {
          fields.remove(field);
        }
      }
    }

    //处理父类字段
    Class superclass = clazz.getSuperclass();
    String superclassName = superclass.getName();
    if (!"java.lang.Object".equals(superclassName)) {
      Map<String, String> param = getParam(superclass);
      fields.putAll(param);
    }
    return fields;
  }
}
